12. 理解监督微调

这一章真正需要理解的只有一句话:

SFT 不是教模型学知识,而是教模型如何像一个助手一样回答问题。

很多人第一次学 SFT,会觉得它和预训练差不多,都是预测下一个 Token。实际上,两者最大的区别不是模型,也不是 Loss,而是"老师到底在教什么"。


一张图看懂整个 LLM 训练流程

Pasted image 20260711104153.png

一句话:

Pretrain = 学知识

SFT = 学礼貌、学聊天、学回答。


一、为什么需要 SFT?

举一个最简单的例子。

假设一个人天天看百科全书。

天天看:

太阳系有八大行星。

木星是最大的行星。

深度学习属于机器学习。

北京是中国首都。

看了十年。

最后问他:

北京在哪?

他可能回答:

北京在哪?
北京是中华人民共和国首都,
位于华北平原……

甚至继续写一整篇百科。

为什么?

因为他一辈子看的都是

一句接一句的文章。

根本没人教过他:

别人提问题,你应该回答。

这就是预训练模型。


预训练模型其实不会聊天

它真正学的是:

前面一句
↓
预测下一句

例如:

天空很蓝,
↓
预测:
今天阳光很好。

再比如

深度学习属于
↓
预测:
机器学习。

它一直都是:

接着写。

不是:

回答问题。


所以你输入

请介绍一下北京

模型心里想的是:

哦,这句话后面应该继续写。

请介绍一下北京,它位于......

而不是

用户问我问题,
我要回答。

所以:

Pretrain 学的是写文章。

不是聊天。


二、SFT 到底教了什么?

SFT 做了一件事情:

告诉模型:"以后看到 User,就回答 Assistant。"

例如:

训练数据变成

User:
北京在哪?

Assistant:
北京位于中国。

模型看到无数这样的例子。

几十万条。

几百万条。

几千万条。

最后模型发现:

原来

User:
xxxx

Assistant:

后面应该生成:

真正的回答。

于是它终于学会:

别人问问题,我回答。


用老师批改作业理解

预训练像这样:

老师说:

今天
↓
继续写。

学生写:

天气很好。

老师打分。

一直如此。


SFT 就变成:

老师说:

用户:

北京在哪?

助手:

学生写:

北京位于中国。

老师打分。

于是模型终于知道:

只有 Assistant 的内容才是我要写的。


三、为什么要 Chat Template?

这是很多初学者最容易忽略的地方。

模型其实根本不知道:

User:
Assistant:

什么意思。

它看到的永远只是:

Token Token Token Token...

所以必须告诉它:

谁在说话。

于是出现 Chat Template。

例如:

<|im_start|>user
北京在哪?
<|im_end|>

<|im_start|>assistant
北京位于中国。
<|im_end|>

对于人来说,这是聊天。

对于模型来说,就是一种固定格式。

像 HTML 一样。


可以画成这样:

聊天记录

User:
北京在哪?

Assistant:
北京位于中国。

变成

<|im_start|>
user
北京在哪?
<|im_end|>

<|im_start|>
assistant
北京位于中国。
<|im_end|>

模型并不知道什么叫 User。

它只知道:

Token 1524

Token 378

Token 980

......

但是由于训练了几百万次。

它渐渐发现:

每次

<|im_start|>assistant

后面都会出现:

回答。

于是形成了条件反射。


四、为什么只计算 Assistant 的 Loss?

这是 SFT 最核心 的知识点。

很多同学第一次都会问:

User 为什么不训练?

答案其实非常简单。

假设:

User:

北京在哪?

Assistant:

北京位于中国。

真正需要模型学习的是哪一句?

当然是:

北京位于中国。

因为:

User 不是模型写的。

而是人输入的。

模型根本不用学。


可以画出来。


User:

北京在哪?

Assistant:

北京位于中国。

Loss:

北京 在 哪?

✗ ✗ ✗

北京 位于 中国。

✓ ✓ ✓ ✓

为什么?

因为:

模型以后运行时,

User 永远都是人输入。

模型负责生成的是:

Assistant。


再举一个更生活化的例子

像考试。

老师发卷子:

1+1=?

学生写:

2

老师评分。

老师会不会给

1+1=?

打分?

不会。

因为:

题目不是学生写的。

学生只负责:

答案。

SFT 完全一样。

User
↓
题目
Assistant
↓
答案

Loss 只看答案。


五、Loss Mask 到底是什么?

其实就是:

哪些 Token 参加考试。

例如:

User:
北京在哪?

Assistant:
北京在中国。

Mask:

User:

0 0 0 0 0

Assistant:

1 1 1 1 1

可以理解成:

老师拿了一支荧光笔。

User

全部划掉。

只保留:

Assistant

最后真正计算 Loss 的只有:

北京 在 中国 。

可以画成下面这样

Token

User 北京 在 哪?

Mask

0 0 0 0

Assistant 北京 位于 中国 。

Mask

1 1 1 1 1

Loss:

0×loss

+

1×loss

所以:

User 不产生梯度

Assistant 产生梯度

六、为什么还要预测 <|im_end|>

很多人第一次都会疑惑:

为什么结束符也要算 Loss?

原因很简单。

否则模型不知道:

什么时候结束。

例如:

User:
北京在哪?

如果没训练

<|im_end|>

模型可能一直说:

北京位于中国。

中国有34个省。

北京有......

......

......

停不下来。

所以要训练:

北京 位于 中国 。

<|im_end|>

模型学会:

回答完了,就结束。


七、Pretrain 和 SFT 的本质区别

可以用一个老师的比喻。


第一阶段(Pretrain)

老师:

把这本百科全书背下来。

学生学到:

大量知识。

第二阶段(SFT)

老师:

以后别人问问题,

不要背百科,

直接回答。

学生学到:

如何交流。

所以:

Pretrain

知识增加 ↑↑↑

聊天能力 ↓

而:

SFT

知识增加 很少

聊天能力 ↑↑↑↑↑

八、一张图总结 SFT

flowchart TD

A["海量文本
Pretrain"] --> B["模型学会知识
语言、数学、代码"] B --> C["SFT 对话数据"] C --> D["Chat Template"] D --> E["Tokenizer"] E --> F["Token序列"] F --> G["Loss Mask"] G --> H["User/System = 0
Assistant = 1"] H --> I["CrossEntropy"] I --> J["只更新回答部分梯度"] J --> K["模型学会如何回答问题"]

九、一句话总结

可以把整个 SFT 浓缩成四句话:

Pretrain 教模型会写,SFT 教模型会答。

Chat Template 负责告诉模型谁在说话。

Loss Mask 负责告诉模型哪些 Token 需要学习。

SFT 本质仍然是 Next Token Prediction,只不过只对 Assistant 回复部分计算 Loss。